--- /dev/null
+use cargo::ops;
+use cargo::core::{MultiShell};
+use cargo::util::{CliResult, CliError};
+
+#[deriving(Decodable)]
+struct Options {
+ flag_host: Option<String>,
+ flag_verbose: bool,
+ arg_query: String
+}
+
+pub const USAGE: &'static str = "
+Search packages in crates.io
+
+Usage:
+ cargo search [options] <query>
+
+Options:
+ -h, --help Print this message
+ --host HOST Host of a registry to search in
+ -v, --verbose Use verbose output
+";
+
+pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+ shell.set_verbose(options.flag_verbose);
+ let Options {
+ flag_host: host,
+ arg_query: query,
+ ..
+ } = options;
+
+ ops::search(query.as_slice(), shell, host)
+ .map(|_| None)
+ .map_err(|err| CliError::from_boxed(err, 101))
+}
use std::collections::HashMap;
use std::io::File;
use std::os;
+use term::color::BLACK;
use curl::http;
use git2;
use ops;
use sources::{PathSource, RegistrySource};
use util::config;
-use util::{CargoResult, human, internal, ChainError, Require, ToUrl};
+use util::{CargoResult, human, internal, ChainError, ToUrl};
use util::config::{Config, ConfigValue, Location};
pub struct RegistryConfig {
token: token_config,
index: index_config,
} = try!(registry_configuration());
- let token = try!(token.or(token_config).require(|| {
- human("no upload token found, please run `cargo login`")
- }));
+ let token = token.or(token_config);
let index = index.or(index_config).unwrap_or(RegistrySource::default_url());
let index = try!(index.as_slice().to_url().map_err(human));
let sid = SourceId::for_registry(&index);
Ok(())
}
+
+pub fn search(query: &str, shell: &mut MultiShell, index: Option<String>) -> CargoResult<()> {
+ fn truncate_with_ellipsis(s: &str, max_length: uint) -> String {
+ if s.len() < max_length {
+ s.to_string()
+ } else {
+ format!("{}…", s[..max_length - 1])
+ }
+ }
+
+ let (mut registry, _) = try!(registry(shell, None, index));
+
+ let crates = try!(registry.search(query).map_err(|e| {
+ human(format!("failed to retrieve search results from the registry: {}", e))
+ }));
+
+ let list_items = crates.iter()
+ .map(|krate| (
+ format!("{} ({})", krate.name, krate.max_version),
+ krate.description.as_ref().map(|desc|
+ truncate_with_ellipsis(desc.replace("\n", " ").as_slice(), 128))
+ ))
+ .collect::<Vec<_>>();
+ let description_margin = list_items.iter()
+ .map(|&(ref left, _)| left.len() + 4)
+ .max()
+ .unwrap_or(0);
+
+ for (name, description) in list_items.into_iter() {
+ let line = match description {
+ Some(desc) => {
+ let space = String::from_char(
+ description_margin - name.len(),
+ ' ');
+ name + space + desc
+ }
+ None => name
+ };
+ try!(shell.say(line, BLACK));
+ }
+
+ Ok(())
+}
pub struct Registry {
host: String,
- token: String,
+ token: Option<String>,
handle: http::Handle,
}
pub type Result<T> = result::Result<T, Error>;
+#[deriving(PartialEq)]
+pub enum Auth {
+ Authorized,
+ Unauthorized
+}
+
pub enum Error {
Curl(curl::ErrCode),
NotOkResponse(http::Response),
NonUtf8Body,
Api(Vec<String>),
Unauthorized,
+ TokenMissing,
Io(io::IoError),
}
+#[deriving(Decodable)]
+pub struct Crate {
+ pub name: String,
+ pub description: Option<String>,
+ pub max_version: String
+}
+
#[deriving(Encodable)]
pub struct NewCrate {
pub name: String,
#[deriving(Decodable)] struct ApiError { detail: String }
#[deriving(Encodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
#[deriving(Decodable)] struct Users { users: Vec<User> }
+#[deriving(Decodable)] struct Crates { crates: Vec<Crate> }
impl Registry {
- pub fn new(host: String, token: String) -> Registry {
+ pub fn new(host: String, token: Option<String>) -> Registry {
Registry::new_handle(host, token, http::Handle::new())
}
- pub fn new_handle(host: String, token: String,
+ pub fn new_handle(host: String, token: Option<String>,
handle: http::Handle) -> Registry {
Registry {
host: host,
box tarball as Box<Reader>].into_iter());
let url = format!("{}/api/v1/crates/new", self.host);
- let response = handle(self.handle.put(url, &mut body)
- .content_length(size)
- .header("Authorization",
- self.token.as_slice())
- .header("Accept", "application/json")
- .exec());
+
+ let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
+ let request = self.handle.put(url, &mut body)
+ .content_length(size)
+ .header("Accept", "application/json")
+ .header("Authorization", token);
+ let response = handle(request.exec());
let _body = try!(response);
Ok(())
}
+ pub fn search(&mut self, query: &str) -> Result<Vec<Crate>> {
+ let body = try!(self.req(format!("/crates?q={}", query), None, Get, Auth::Unauthorized));
+
+ Ok(json::decode::<Crates>(body.as_slice()).unwrap().crates)
+ }
+
pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
let body = try!(self.delete(format!("/crates/{}/{}/yank", krate, version),
None));
}
fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
- self.req(path, Some(b), Put)
+ self.req(path, Some(b), Put, Auth::Authorized)
}
fn get(&mut self, path: String) -> Result<String> {
- self.req(path, None, Get)
+ self.req(path, None, Get, Auth::Authorized)
}
fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result<String> {
- self.req(path, b, Delete)
+ self.req(path, b, Delete, Auth::Authorized)
}
fn req(&mut self, path: String, body: Option<&[u8]>,
- method: Method) -> Result<String> {
+ method: Method, authorized: Auth) -> Result<String> {
let mut req = Request::new(&mut self.handle, method)
.uri(format!("{}/api/v1{}", self.host, path))
- .header("Authorization", self.token.as_slice())
.header("Accept", "application/json")
.content_type("application/json");
+
+ let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
+ if authorized == Auth::Authorized {
+ req = req.header("Authorization", token);
+ }
match body {
Some(b) => req = req.body(b),
None => {}
write!(f, "api errors: {}", errs.connect(", "))
}
Error::Unauthorized => write!(f, "unauthorized API access"),
+ Error::TokenMissing => write!(f, "no upload token found, please run `cargo login`"),
Error::Io(ref e) => write!(f, "io error: {}", e),
}
}